Refactor project structure, enhance UI components, and implement features - #43
Refactor project structure, enhance UI components, and implement features#43mrboxs wants to merge 12 commits into
Conversation
…and bump dependencies
- Add new UI components: dialog, empty, input, separator, sheet, sidebar, skeleton, tooltip, use-mobile hook - Add app layout with _app route and theme provider - Add error/loading/not-found/pending components - Add TanStack Router progress indicator - Update button component variant - Restructure routes under _app layout
…, and skeleton loaders
…components for toast notifications and cards
…n and database schema update
…ser avatar generation
… and fix thread list imports
…vote gating to thread list
WalkthroughThis PR transforms the frontend into a fully functional thread discussion application with user authentication, thread listing/voting, and a comprehensive UI component library. It introduces nested routing structures ( ChangesFrontend Application Architecture & User Experience
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/auth/src/index.ts (1)
91-98:⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy liftCritical:
usernamedefault is generated once, causing unique collisionsIn
packages/auth/src/index.ts(lines 91-98),defaultValue: \u_${crypto.randomUUID().slice(0, 8)}`is evaluated when the auth instance/config is created, so every user created without an explicitusernamewill share the sameusername. Withunique: true`, this will make subsequent signups/creations fail.Generate the username per user instead (e.g., via a user-creation hook/callback or a DB-side default/trigger), rather than relying on the current
defaultValueexpression.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/auth/src/index.ts` around lines 91 - 98, The username field's defaultValue (`defaultValue: \`u_${crypto.randomUUID().slice(0, 8)}\``) is evaluated once at module load causing duplicate usernames; change generation to run per-user instead by removing the static defaultValue and implementing a per-creation generator (e.g., in the user model's creation hook such as a beforeCreate/onCreate callback) that assigns `username` when absent, or use a DB-side default/trigger; update the code referencing the username field (the username property in packages/auth/src/index.ts) to rely on that hook/generator so each new user receives a unique `u_<random>` value.
🧹 Nitpick comments (1)
packages/ui/src/styles/globals.css (1)
4-4: ⚡ Quick winDrop the now-unused Outfit font import.
--font-sansis set to'Inter Variable', and--font-headingmaps tovar(--font-sans), so Outfit is never referenced beyond@import '@fontsource-variable/outfit';. Removing it avoids shipping an unused font file.♻️ Remove unused import
-@import '`@fontsource-variable/outfit`'; `@import` '`@fontsource-variable/inter`';🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/styles/globals.css` at line 4, The file contains an unused font import "`@fontsource-variable/outfit`" that is never referenced because --font-sans is set to "Inter Variable" and --font-heading uses var(--font-sans); remove the `@import` '`@fontsource-variable/outfit`'; line from packages/ui/src/styles/globals.css so the unused Outfit asset is no longer shipped.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/src/components/error-components.tsx`:
- Around line 43-45: The effect currently depends on queryClientErrorBoundary
which may be a new object each render; change the useEffect in the error
component so queryClientErrorBoundary.reset() runs only on mount (replace the
dependency array [queryClientErrorBoundary] with []), or if ESLint complains,
keep the empty array and add a concise comment/disable for the specific
exhaustive-deps rule; reference the useEffect that calls
queryClientErrorBoundary.reset and the hook useQueryErrorResetBoundary() that
provides queryClientErrorBoundary.
In `@apps/web/src/components/navbar.tsx`:
- Around line 17-27: Navbar's aria-label and icon logic uses only state and
isMobile, causing the mobile button to always show the "Expand" variant; update
the condition to check openMobile when isMobile. Modify the JSX where aria-label
and icon are chosen (the conditional using state === 'collapsed' || isMobile) to
instead use (isMobile ? openMobile : state !== 'collapsed') or equivalent so
that when isMobile the label/icon reflect openMobile; ensure you reference the
openMobile boolean from the same props/state used by toggleSidebar and update
both the aria-label string and the icon selection (PanelLeftIcon vs
PanelLeftCloseIcon) accordingly.
In `@apps/web/src/components/sign-in-social-buttons.tsx`:
- Around line 18-29: The declaration of socialButtons uses "as const" which
makes a readonly tuple conflicting with the mutable Array<SocialButton> type;
remove the trailing "as const" from the socialButtons initializer (or
alternatively change the SocialButton type/array declaration to readonly) so the
literal array's mutability matches the declared type; locate the socialButtons
constant and adjust the "as const" usage or convert
SocialButton/Array<SocialButton> to readonly to resolve the type mismatch.
In `@apps/web/src/features/threads/components/thread-card.tsx`:
- Around line 92-97: The Link for the thread title in thread-card.tsx currently
uses to="." which keeps users on the current list page; change it to point to
the thread detail route by replacing the to="." on the Link (in
apps/web/src/features/threads/components/thread-card.tsx) with the correct
destination for a single thread—either use the generated route helper from
apps/web/src/routeTree.gen.ts (e.g., the route helper for the thread detail)
supplying thread.slug, or if no helper exists add a thread detail route (e.g.,
/threads/$slug) to the route tree and then link using that route with
thread.slug so clicking the title navigates to the thread detail page instead of
the current route.
In `@apps/web/src/routes/_auth.tsx`:
- Around line 8-13: The sanitizeCallbackURL function currently allows inputs
like "/\evil.com" because it only checks startsWith('/') and not for
backslashes; update sanitizeCallbackURL to explicitly reject or normalize any
URL containing backslash characters before accepting it—e.g., if url contains
'\\' return '/' (or normalize by replacing backslashes then re-validate), and
keep the existing checks for url.startsWith('/') and !url.startsWith('//') so
that inputs with backslashes cannot be transformed by browser normalization into
protocol-relative or external URLs; refer to the sanitizeCallbackURL function to
implement this validation.
In `@apps/web/src/routes/_auth/on-boarding.tsx`:
- Around line 47-56: The regex in onBoardingFormSchema.username uses {1,28}
which yields total length 2–29, conflicting with .min(3) and .max(30); update
the quantifier in the regex (the pattern in onBoardingFormSchema for username)
to {2,29} so the regex enforces total length 3–30 and still preserves the
start/end and hyphen rules.
In `@packages/ui/src/components/dialog.tsx`:
- Line 4: The import in components/dialog.tsx is using the wrong Radix package
path; replace the incorrect import from 'radix-ui' with the scoped package
'`@radix-ui/react-dialog`' so the Dialog component (imported as Dialog or
DialogPrimitive) resolves correctly; locate the import statement referencing
Dialog (e.g., "import { Dialog as DialogPrimitive }") and update its module
specifier to '`@radix-ui/react-dialog`' and then run the repo type-check/build to
confirm the component compiles.
In `@packages/ui/src/components/separator.tsx`:
- Around line 18-21: The Tailwind data selectors are wrong for
SeparatorPrimitive.Root: replace the current data-horizontal:* and
data-vertical:* utilities in the className passed to the Separator component
with Tailwind data attribute selectors that match Radix's data-orientation, e.g.
use data-[orientation=horizontal]:h-px and data-[orientation=horizontal]:w-full
and data-[orientation=vertical]:w-px and
data-[orientation=vertical]:self-stretch (keep the cn(...) wrapper and preserve
the existing 'shrink-0 bg-border' and the passed className).
---
Outside diff comments:
In `@packages/auth/src/index.ts`:
- Around line 91-98: The username field's defaultValue (`defaultValue:
\`u_${crypto.randomUUID().slice(0, 8)}\``) is evaluated once at module load
causing duplicate usernames; change generation to run per-user instead by
removing the static defaultValue and implementing a per-creation generator
(e.g., in the user model's creation hook such as a beforeCreate/onCreate
callback) that assigns `username` when absent, or use a DB-side default/trigger;
update the code referencing the username field (the username property in
packages/auth/src/index.ts) to rely on that hook/generator so each new user
receives a unique `u_<random>` value.
---
Nitpick comments:
In `@packages/ui/src/styles/globals.css`:
- Line 4: The file contains an unused font import "`@fontsource-variable/outfit`"
that is never referenced because --font-sans is set to "Inter Variable" and
--font-heading uses var(--font-sans); remove the `@import`
'`@fontsource-variable/outfit`'; line from packages/ui/src/styles/globals.css so
the unused Outfit asset is no longer shipped.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2ccb4281-4e30-4263-9481-98b107befb54
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (74)
apps/web/package.jsonapps/web/src/components/avatar-user.tsxapps/web/src/components/error-components.tsxapps/web/src/components/generated-avatar.tsxapps/web/src/components/loader.tsxapps/web/src/components/marv-icon.tsxapps/web/src/components/navbar.tsxapps/web/src/components/not-found-components.tsxapps/web/src/components/pending-components.tsxapps/web/src/components/sidebar-content.tsxapps/web/src/components/sidebar-footer.tsxapps/web/src/components/sidebar-header.tsxapps/web/src/components/sidebar-wrapper.tsxapps/web/src/components/sign-in-social-buttons.tsxapps/web/src/components/tanstack-router-progress-provider.tsxapps/web/src/components/tanstack-router-progress.tsxapps/web/src/components/theme-provider.tsxapps/web/src/features/threads/components/thread-card.tsxapps/web/src/features/threads/components/thread-list-card.tsxapps/web/src/features/threads/hooks/use-threads.tsapps/web/src/features/threads/utils/thread-query-options.tsapps/web/src/features/votes/hooks/use-votes.tsapps/web/src/features/votes/utils/votes-optimistic-cache.tsapps/web/src/integrations/tanstack-query/devtools.tsxapps/web/src/integrations/tanstack-query/root-provider.tsxapps/web/src/libs/auth-client.tsapps/web/src/libs/orpc.tsapps/web/src/libs/query-client.tsapps/web/src/routeTree.gen.tsapps/web/src/router.tsxapps/web/src/routes/__root.tsxapps/web/src/routes/_app.tsxapps/web/src/routes/_app/index.tsxapps/web/src/routes/_auth.tsxapps/web/src/routes/_auth/on-boarding.tsxapps/web/src/routes/_auth/sign-in.tsxapps/web/src/routes/index.tsxopencode.jsonpackage.jsonpackages/api/src/routers/threads.router.tspackages/api/src/routers/votes.router.tspackages/auth/src/index.tspackages/db/src/queries/reply.query.tspackages/db/src/queries/thread.query.tspackages/db/src/queries/vote.query.tspackages/db/src/schemas/auth.tspackages/shared/src/constants/index.tspackages/shared/src/schemas/threads.test.tspackages/shared/src/schemas/threads.tspackages/shared/src/schemas/votes.tspackages/ui/components.jsonpackages/ui/package.jsonpackages/ui/src/components/avatar.tsxpackages/ui/src/components/button-group.tsxpackages/ui/src/components/button.tsxpackages/ui/src/components/card.tsxpackages/ui/src/components/dialog.tsxpackages/ui/src/components/drawer.tsxpackages/ui/src/components/dropdown-menu.tsxpackages/ui/src/components/empty.tsxpackages/ui/src/components/field.tsxpackages/ui/src/components/input-group.tsxpackages/ui/src/components/input.tsxpackages/ui/src/components/label.tsxpackages/ui/src/components/separator.tsxpackages/ui/src/components/sheet.tsxpackages/ui/src/components/sidebar.tsxpackages/ui/src/components/skeleton.tsxpackages/ui/src/components/sonner.tsxpackages/ui/src/components/spinner.tsxpackages/ui/src/components/textarea.tsxpackages/ui/src/components/tooltip.tsxpackages/ui/src/hooks/use-mobile.tspackages/ui/src/styles/globals.css
💤 Files with no reviewable changes (3)
- apps/web/src/routes/index.tsx
- apps/web/src/integrations/tanstack-query/root-provider.tsx
- apps/web/src/integrations/tanstack-query/devtools.tsx
| useEffect(() => { | ||
| queryClientErrorBoundary.reset() | ||
| }, [queryClientErrorBoundary]) |
There was a problem hiding this comment.
Review the useEffect dependency array.
The useEffect includes queryClientErrorBoundary in the dependency array. If useQueryErrorResetBoundary() returns a new object reference on each render, this effect will run repeatedly instead of only once on mount. Typically, the error boundary reset should only run once when the error component mounts.
♻️ Suggested fix
useEffect(() => {
queryClientErrorBoundary.reset()
-}, [queryClientErrorBoundary])
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+}, [])📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| queryClientErrorBoundary.reset() | |
| }, [queryClientErrorBoundary]) | |
| useEffect(() => { | |
| queryClientErrorBoundary.reset() | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, []) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/components/error-components.tsx` around lines 43 - 45, The
effect currently depends on queryClientErrorBoundary which may be a new object
each render; change the useEffect in the error component so
queryClientErrorBoundary.reset() runs only on mount (replace the dependency
array [queryClientErrorBoundary] with []), or if ESLint complains, keep the
empty array and add a concise comment/disable for the specific exhaustive-deps
rule; reference the useEffect that calls queryClientErrorBoundary.reset and the
hook useQueryErrorResetBoundary() that provides queryClientErrorBoundary.
| aria-label={ | ||
| state === 'collapsed' || isMobile | ||
| ? 'Expand sidebar' | ||
| : 'Collapse sidebar' | ||
| } | ||
| > | ||
| {state === 'collapsed' || isMobile ? ( | ||
| <PanelLeftIcon /> | ||
| ) : ( | ||
| <PanelLeftCloseIcon /> | ||
| )} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify useSidebar exposes openMobile/state so the toggle can reflect the actual open state on mobile.
ast-grep --pattern 'const useSidebar = () => { $$$ }'
rg -nP '\b(openMobile|isMobile|state)\b' packages/ui/src/components/sidebar.tsx -C2Repository: isntboxs/marvticle
Length of output: 3623
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the navbar toggle code around the referenced lines
echo "---- navbar.tsx (lines 1-80) ----"
nl -ba apps/web/src/components/navbar.tsx | sed -n '1,120p'
# Find where openMobile is referenced/consumed in navbar.tsx
echo "---- search openMobile usage in navbar.tsx ----"
rg -n "openMobile|setOpenMobile" apps/web/src/components/navbar.tsx || true
# Locate how navbar imports/uses the sidebar hook/context
echo "---- search for useSidebar usage in navbar.tsx ----"
rg -n "useSidebar" apps/web/src/components/navbar.tsx
# Inspect sidebar hook return shape for what navbar might be able to use
echo "---- sidebar implementation snippet (useSidebar context) ----"
rg -n "openMobile|isMobile|toggleSidebar" packages/ui/src/components/sidebar.tsx -nRepository: isntboxs/marvticle
Length of output: 157
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show navbar.tsx with line numbers (using awk instead of nl)
echo "---- navbar.tsx (first 160 lines) ----"
awk '{printf "%5d\t%s\n", NR, $0}' apps/web/src/components/navbar.tsx | sed -n '1,160p'
echo "---- search openMobile usage in navbar.tsx ----"
rg -n "openMobile|setOpenMobile" apps/web/src/components/navbar.tsx || true
echo "---- search useSidebar usage in navbar.tsx ----"
rg -n "useSidebar" apps/web/src/components/navbar.tsx || true
echo "---- show relevant snippet around the toggle (look for Expand/Collapse sidebar strings) ----"
rg -n "Expand sidebar|Collapse sidebar" apps/web/src/components/navbar.tsx -n -C3 || trueRepository: isntboxs/marvticle
Length of output: 1710
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the relevant parts of sidebar.tsx: context shape, state/openMobile derivation, and isMobile computation
echo "---- sidebar.tsx: SidebarContextProps + hook return (first 220 lines) ----"
awk '{printf "%5d\t%s\n", NR, $0}' packages/ui/src/components/sidebar.tsx | sed -n '1,240p'
echo "---- sidebar.tsx: useIsMobile + toggleSidebar + state/openMobile (around toggle/state) ----"
rg -n "useIsMobile|toggleSidebar|openMobile|const state" packages/ui/src/components/sidebar.tsx -n
echo "---- sidebar.tsx: exact lines around openMobile & state (lines 55-140) ----"
awk '{printf "%5d\t%s\n", NR, $0}' packages/ui/src/components/sidebar.tsx | sed -n '50,160p'
echo "---- sidebar.tsx: mobile rendering uses openMobile (around isMobile block) ----"
awk '{printf "%5d\t%s\n", NR, $0}' packages/ui/src/components/sidebar.tsx | sed -n '160,230p'Repository: isntboxs/marvticle
Length of output: 15306
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "function useIsMobile|export const useIsMobile|export function useIsMobile" -S packages/ui/src/hooks/use-mobile* packages/ui/src/hooks -g'*.ts' -g'*.tsx'Repository: isntboxs/marvticle
Length of output: 202
🏁 Script executed:
#!/bin/bash
set -euo pipefail
awk '{printf "%5d\t%s\n", NR, $0}' packages/ui/src/hooks/use-mobile.ts | sed -n '1,120p'Repository: isntboxs/marvticle
Length of output: 722
Fix toggle label/icon to reflect mobile sidebar open state.
On mobile, isMobile is driven by viewport width, so Navbar’s condition (state === 'collapsed' || isMobile ? 'Expand sidebar' : 'Collapse sidebar' and the icon) always picks the “Expand” variant even when the mobile Sheet is open. toggleSidebar toggles openMobile, but Navbar only reads state and isMobile.
Update Navbar to also use openMobile and switch the aria-label/icon based on openMobile when isMobile.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/components/navbar.tsx` around lines 17 - 27, Navbar's aria-label
and icon logic uses only state and isMobile, causing the mobile button to always
show the "Expand" variant; update the condition to check openMobile when
isMobile. Modify the JSX where aria-label and icon are chosen (the conditional
using state === 'collapsed' || isMobile) to instead use (isMobile ? openMobile :
state !== 'collapsed') or equivalent so that when isMobile the label/icon
reflect openMobile; ensure you reference the openMobile boolean from the same
props/state used by toggleSidebar and update both the aria-label string and the
icon selection (PanelLeftIcon vs PanelLeftCloseIcon) accordingly.
| const socialButtons: Array<SocialButton> = [ | ||
| { | ||
| provider: 'github', | ||
| icon: FaGithub, | ||
| label: 'GitHub', | ||
| }, | ||
| { | ||
| provider: 'google', | ||
| icon: FcGoogle, | ||
| label: 'Google', | ||
| }, | ||
| ] as const |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm the file compiles; expect a readonly-vs-mutable assignment error if `as const` is kept with Array<SocialButton>.
fd -t f tsconfig.json apps/web
rg -nP 'as const' apps/web/src/components/sign-in-social-buttons.tsxRepository: isntboxs/marvticle
Length of output: 99
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="apps/web/src/components/sign-in-social-buttons.tsx"
echo "---- sign-in-social-buttons.tsx (relevant lines) ----"
nl -ba "$FILE" | sed -n '1,120p'
echo "---- Find SocialButton definition/usages ----"
rg -n --hidden --no-ignore -S "type\s+SocialButton|interface\s+SocialButton|SocialButton" apps/web/src | head -n 50
echo "---- Inspect the inferred type of socialButtons by searching its declaration context ----"
rg -n "socialButtons\s*:" "$FILE"
echo "---- Check if socialButtons is consumed in a way that requires readonly or mutable array ----"
rg -n --hidden --no-ignore -S "socialButtons" apps/web/src/components/sign-in-social-buttons.tsxRepository: isntboxs/marvticle
Length of output: 177
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="apps/web/src/components/sign-in-social-buttons.tsx"
echo "---- sign-in-social-buttons.tsx (relevant lines) ----"
cat -n "$FILE" | sed -n '1,160p'
echo "---- Find SocialButton definition/usages ----"
rg -n --hidden --no-ignore -S "type\s+SocialButton|interface\s+SocialButton|class\s+SocialButton|enum\s+SocialButton|\bSocialButton\b" apps/web/src | head -n 80
echo "---- Find socialButtons declaration context ----"
rg -n "socialButtons\s*:" "$FILE"
echo "---- Check if socialButtons is consumed in a way that requires readonly or mutable array ----"
rg -n --hidden --no-ignore -S "\bsocialButtons\b" "$FILE" apps/web/src | head -n 80Repository: isntboxs/marvticle
Length of output: 2855
Fix readonly as const vs mutable Array<SocialButton> mismatch in socialButtons
as const turns the array into a readonly tuple with readonly element properties, which can’t be assigned to the mutable Array<SocialButton> (where SocialButton’s fields are not readonly). Remove as const (or make the SocialButton/array types readonly consistently).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/components/sign-in-social-buttons.tsx` around lines 18 - 29, The
declaration of socialButtons uses "as const" which makes a readonly tuple
conflicting with the mutable Array<SocialButton> type; remove the trailing "as
const" from the socialButtons initializer (or alternatively change the
SocialButton type/array declaration to readonly) so the literal array's
mutability matches the declared type; locate the socialButtons constant and
adjust the "as const" usage or convert SocialButton/Array<SocialButton> to
readonly to resolve the type mismatch.
| <Link | ||
| to="." | ||
| className="text-xl leading-tight font-bold transition-all duration-500 ease-in-out hover:text-primary/90" | ||
| > | ||
| {thread.title} | ||
| </Link> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find the thread detail route definition and its expected params
fd -e tsx . apps/web/src/routes | xargs rg -nl 'slug' 2>/dev/null
rg -nP 'createFileRoute\(' -g 'apps/web/src/routes/**' -C2Repository: isntboxs/marvticle
Length of output: 2415
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Inspect the thread-card link usage around the reported lines
sed -n '70,120p' apps/web/src/features/threads/components/thread-card.tsx | nl -ba
# 2) Find any routes that look like thread detail (param names like $slug/$id)
fd -e tsx apps/web/src/routes | rg -n --hidden -S "(threads|thread)" | head -n 50
# 3) Specifically search for createFileRoute paths containing likely thread params
rg -n "createFileRoute\\('(/[^']*thread[^']*|/[^']*threads[^']*)'" apps/web/src/routes --glob='*.tsx' || true
# 4) Search for dynamic params used in routes
rg -n "\\$slug|\\$id|\\$thread|slug\\b" apps/web/src/routes --glob='*.tsx' | head -n 80
# 5) Show any routes under threads/ directory (if it exists)
ls -la apps/web/src/routes || true
find apps/web/src/routes -maxdepth 3 -type f -name '*thread*' -o -name '*threads*' | sed 's|^|FOUND: |' || trueRepository: isntboxs/marvticle
Length of output: 105
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Inspect the thread-card link usage around the reported lines
echo "---- thread-card.tsx (lines 70-120) ----"
sed -n '70,120p' apps/web/src/features/threads/components/thread-card.tsx | cat -n
# 2) Find any route files that look like thread detail
echo "---- candidate route files under apps/web/src/routes ----"
fd -e tsx 'thread*.tsx' apps/web/src/routes apps/web/src/routes || true
fd -e tsx '*thread*' apps/web/src/routes || true
fd -e tsx '*threads*' apps/web/src/routes || true
# 3) Search for dynamic params used in routes
echo "---- route dynamic params: $slug/$id/$thread ----"
rg -n --hidden "\\$slug|\\$id|\\$thread" apps/web/src/routes --glob='*.tsx' || true
# 4) Search for createFileRoute that may represent thread detail pages
echo "---- createFileRoute paths containing thread/threads ----"
rg -n --hidden "createFileRoute\\('(/[^']*(thread|threads)[^']*)'\\)" apps/web/src/routes --glob='*.tsx' || trueRepository: isntboxs/marvticle
Length of output: 2431
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- thread-card.tsx (lines 1-220) ----"
sed -n '1,220p' apps/web/src/features/threads/components/thread-card.tsx | cat -n
echo
echo "---- list routes (depth 4) ----"
find apps/web/src/routes -maxdepth 4 -type f | sed 's|^|ROUTE: |' | head -n 200
echo
echo "---- route dynamic params in routes (look for $something) ----"
rg -n --hidden '\\$[A-Za-z_][A-Za-z0-9_]*' apps/web/src/routes --glob='*.tsx' || true
echo
echo "---- route files mentioning 'thread' or 'threads' ----"
rg -n --hidden 'threads|thread' apps/web/src/routes --glob='*.tsx' | head -n 200 || true
echo
echo "---- candidate createFileRoute('...') paths containing thread/threads ----"
rg -n --hidden "createFileRoute\\('(/[^']*(thread|threads)[^']*)'\\)" apps/web/src/routes --glob='*.tsx' || trueRepository: isntboxs/marvticle
Length of output: 6571
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- total route tsx files ----"
find apps/web/src/routes -type f -name '*.tsx' | wc -l
echo
echo "---- route files with 'thread'/'threads' in filename ----"
fd --type f -e tsx --glob '*thread*' apps/web/src/routes || true
fd --type f -e tsx --glob '*threads*' apps/web/src/routes || true
echo
echo "---- routes whose createFileRoute path mentions thread/threads ----"
rg -n "createFileRoute\\('([^']*(thread|threads)[^']*)'\\)" apps/web/src/routes --glob='*.tsx' || true
echo
echo "---- dynamic params in route definitions ($slug-like) ----"
rg -n --hidden "\\$[A-Za-z_][A-Za-z0-9_]*" apps/web/src/routes --glob='*.tsx' || true
echo
echo "---- show all route createFileRoute paths that contain /threads/ ----"
rg -n "createFileRoute\\('([^']*/threads/[^']*)'\\)" apps/web/src/routes --glob='*.tsx' || true
echo
echo "---- show all route createFileRoute paths that contain /thread/ ----"
rg -n "createFileRoute\\('([^']*/thread/[^']*)'\\)" apps/web/src/routes --glob='*.tsx' || trueRepository: isntboxs/marvticle
Length of output: 286
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- count of all route files (any extension) ----"
find apps/web/src/routes -type f | wc -l
echo
echo "---- all files under apps/web/src/routes containing 'thread'/'threads' (any ext) ----"
rg -n --hidden --no-ignore -S "threads|thread" apps/web/src/routes || true
echo
echo "---- createFileRoute occurrences under apps/web/src/routes (all ext) ----"
rg -n --hidden "createFileRoute\\('" apps/web/src/routes || true
echo
echo "---- search for likely slug params in routes without using '$' in bash strings ----"
rg -n --hidden "slug" apps/web/src/routes || trueRepository: isntboxs/marvticle
Length of output: 2192
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- repo-wide search for thread detail route patterns ----"
rg -n --hidden --no-ignore -S "createFileRoute\\('([^']*(thread|threads)[^']*)'\\)" . || true
echo
echo "---- repo-wide search for route paths containing /threads/ or /thread/ ----"
rg -n --hidden --no-ignore -S "['\"]/(threads|thread)/\\$" . || true
echo
echo "---- repo-wide search for tanstack route usage with $slug ----"
rg -n --hidden --no-ignore -S "to=\\{?.*\\$slug|\\$slug" apps/web/src . || true
echo
echo "---- search for Thread detail route naming in app (components/hooks/routes) ----"
rg -n --hidden --no-ignore -S "(thread detail|thread-details|ThreadDetails|ThreadDetail)" apps/web/src || true
echo
echo "---- search for thread-card Link targets in other components ----"
rg -n --hidden --no-ignore -S "thread-card|to=\"\\.\"|to=\"\\.\"" apps/web/src/features/threads || trueRepository: isntboxs/marvticle
Length of output: 261
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- repo-wide search for TanStack route definitions that mention thread(s) ----"
rg -n --hidden --no-ignore -S "createFileRoute\\('([^']*(thread|threads)[^']*)'\\)" . || true
echo
echo "---- repo-wide search for route path segments containing /threads/ or /thread/ (string literals) ----"
rg -n --hidden --no-ignore -S "['\"]/threads/|['\"]/thread/" . || true
echo
echo "---- search for thread-detail style route patterns (e.g., /threads/$..., threads/$...) ----"
rg -n --hidden --no-ignore -S "/threads/\\$|threads/\\$|/thread/\\$|thread/\\$" . || true
echo
echo "---- search for thread-card Link targets patterns in repo ----"
rg -n --hidden --no-ignore -S "features/threads|ThreadCard|to=\"\\.\"|to='\\.'|viewTransition" apps/web/src || true
echo
echo "---- list all createFileRoute paths (to see what routes exist) ----"
rg -n --hidden --no-ignore -S "createFileRoute\\('" apps/web/src -g'*.ts*' || trueRepository: isntboxs/marvticle
Length of output: 3916
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- createFileRoute occurrences anywhere in repo ----"
rg -n --hidden --no-ignore "createFileRoute\\('" . || true
echo
echo "---- Link targets that look like thread detail: /threads/<slug> or /threads/ ----"
rg -n --hidden --no-ignore -S "to=\\\"/threads/\\$\\{|to=\\{\\s*`/threads/\\$\\{|to=\\\"/threads/|to=\\{\\s*`/threads/" apps/web/src || true
rg -n --hidden --no-ignore -S "to=\\\"/threads/|to=\\{.*\\/threads\\/" apps/web/src || true
rg -n --hidden --no-ignore -S "/threads/\\$\\{?slug\\}?|/threads/\\{slug\\}" apps/web/src || true
echo
echo "---- router setup: find Route definitions / router provider wiring ----"
rg -n --hidden --no-ignore -S "RouterProvider|routeTree|createRouter|useRouter\\(" apps/web/src || true
rg -n --hidden --no-ignore -S "routeTree\\.|generatedRouteTree|__routeTree|routeTree\\.ts" apps/web/src || true
echo
echo "---- inspect thread-list-card to see what the page route is for list ----"
sed -n '1,220p' apps/web/src/features/threads/components/thread-list-card.tsx | cat -nRepository: isntboxs/marvticle
Length of output: 9054
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- routeTree.gen.ts: search for /threads and threads ----"
rg -n --hidden --no-ignore -S "/threads|threads" apps/web/src/routeTree.gen.ts || true
echo
echo "---- routeTree.gen.ts: search for slug param in routeTree ----"
rg -n --hidden --no-ignore -S "slug" apps/web/src/routeTree.gen.ts || true
echo
echo "---- full repo: search for routeTree.gen references containing threads ----"
rg -n --hidden --no-ignore -S "routeTree\\.gen|routeTree" apps/web/src | head -n 50Repository: isntboxs/marvticle
Length of output: 448
Fix thread title navigation (currently links to the current route)
In apps/web/src/features/threads/components/thread-card.tsx (lines 92-97), the thread title Link uses to=".", so it resolves to the current page (thread list) instead of a thread detail page.
<Link
to="."
className="text-xl leading-tight font-bold transition-all duration-500 ease-in-out hover:text-primary/90"
>
{thread.title}
</Link>The client route tree (apps/web/src/routes / apps/web/src/routeTree.gen.ts) doesn’t include a thread detail route (e.g. /threads/$slug), so the fix is to either add that route and link it using thread.slug, or update this Link to the correct existing thread-detail destination.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/features/threads/components/thread-card.tsx` around lines 92 -
97, The Link for the thread title in thread-card.tsx currently uses to="." which
keeps users on the current list page; change it to point to the thread detail
route by replacing the to="." on the Link (in
apps/web/src/features/threads/components/thread-card.tsx) with the correct
destination for a single thread—either use the generated route helper from
apps/web/src/routeTree.gen.ts (e.g., the route helper for the thread detail)
supplying thread.slug, or if no helper exists add a thread detail route (e.g.,
/threads/$slug) to the route tree and then link using that route with
thread.slug so clicking the title navigates to the thread detail page instead of
the current route.
| function sanitizeCallbackURL(url: string): string { | ||
| if (!url.startsWith('/') || url.startsWith('//')) { | ||
| return '/' | ||
| } | ||
| return url | ||
| } |
There was a problem hiding this comment.
Harden sanitizeCallbackURL against backslash open-redirect bypass.
Values like /\evil.com or /\/evil.com start with a single / and pass this check, but many browsers normalize backslashes to /, turning them into protocol-relative URLs. Normalize/reject backslashes too.
🛡️ Proposed fix
function sanitizeCallbackURL(url: string): string {
- if (!url.startsWith('/') || url.startsWith('//')) {
+ if (!url.startsWith('/') || url.startsWith('//') || /^\/[\\/]/.test(url)) {
return '/'
}
return url
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function sanitizeCallbackURL(url: string): string { | |
| if (!url.startsWith('/') || url.startsWith('//')) { | |
| return '/' | |
| } | |
| return url | |
| } | |
| function sanitizeCallbackURL(url: string): string { | |
| if (!url.startsWith('/') || url.startsWith('//') || /^\/[\\/]/.test(url)) { | |
| return '/' | |
| } | |
| return url | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/routes/_auth.tsx` around lines 8 - 13, The sanitizeCallbackURL
function currently allows inputs like "/\evil.com" because it only checks
startsWith('/') and not for backslashes; update sanitizeCallbackURL to
explicitly reject or normalize any URL containing backslash characters before
accepting it—e.g., if url contains '\\' return '/' (or normalize by replacing
backslashes then re-validate), and keep the existing checks for
url.startsWith('/') and !url.startsWith('//') so that inputs with backslashes
cannot be transformed by browser normalization into protocol-relative or
external URLs; refer to the sanitizeCallbackURL function to implement this
validation.
| const onBoardingFormSchema = z.object({ | ||
| username: z | ||
| .string() | ||
| .min(3, 'Username must be at least 3 characters long') | ||
| .max(30, 'Username must be at most 30 characters long') | ||
| .regex( | ||
| /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){1,28}$/i, | ||
| 'Username must be at least 3 characters long, can only contain letters, numbers, and hyphens, and must start and end with a letter or number' | ||
| ), | ||
| }) |
There was a problem hiding this comment.
Regex upper bound conflicts with max(30).
The regex group {1,28} caps total length at 29 chars (1 leading + 1–28), while .max(30) permits 30. A 30-char alphanumeric username passes max but fails the regex, surfacing the misleading "at least 3 characters" message. Align the bounds.
🔧 Proposed fix
.regex(
- /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){1,28}$/i,
+ /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){1,29}$/i,
'Username must be at least 3 characters long, can only contain letters, numbers, and hyphens, and must start and end with a letter or number'
),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const onBoardingFormSchema = z.object({ | |
| username: z | |
| .string() | |
| .min(3, 'Username must be at least 3 characters long') | |
| .max(30, 'Username must be at most 30 characters long') | |
| .regex( | |
| /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){1,28}$/i, | |
| 'Username must be at least 3 characters long, can only contain letters, numbers, and hyphens, and must start and end with a letter or number' | |
| ), | |
| }) | |
| const onBoardingFormSchema = z.object({ | |
| username: z | |
| .string() | |
| .min(3, 'Username must be at least 3 characters long') | |
| .max(30, 'Username must be at most 30 characters long') | |
| .regex( | |
| /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){1,29}$/i, | |
| 'Username must be at least 3 characters long, can only contain letters, numbers, and hyphens, and must start and end with a letter or number' | |
| ), | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/routes/_auth/on-boarding.tsx` around lines 47 - 56, The regex in
onBoardingFormSchema.username uses {1,28} which yields total length 2–29,
conflicting with .min(3) and .max(30); update the quantifier in the regex (the
pattern in onBoardingFormSchema for username) to {2,29} so the regex enforces
total length 3–30 and still preserves the start/end and hyphen rules.
| import * as React from 'react' | ||
|
|
||
| import { IconX } from '@tabler/icons-react' | ||
| import { Dialog as DialogPrimitive } from 'radix-ui' |
There was a problem hiding this comment.
Fix incorrect Radix UI import path.
The import path 'radix-ui' appears incorrect. Radix UI packages are scoped under @radix-ui/. The correct import should be '@radix-ui/react-dialog'.
🐛 Proposed fix
-import { Dialog as DialogPrimitive } from 'radix-ui'
+import * as DialogPrimitive from '`@radix-ui/react-dialog`'Run the following script to verify the correct Radix UI package:
#!/bin/bash
# Description: Check for Radix UI Dialog package in dependencies
# Check package.json for radix-ui dependencies
fd -t f 'package.json' --exec jq -r '.dependencies, .devDependencies | to_entries[] | select(.key | contains("radix")) | "\(.key): \(.value)"' {} \;
# Search for other Radix UI imports in the codebase
rg -n '`@radix-ui/react-`' --type=typescript --type=tsx -g '*.{ts,tsx}' | head -20🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/components/dialog.tsx` at line 4, The import in
components/dialog.tsx is using the wrong Radix package path; replace the
incorrect import from 'radix-ui' with the scoped package
'`@radix-ui/react-dialog`' so the Dialog component (imported as Dialog or
DialogPrimitive) resolves correctly; locate the import statement referencing
Dialog (e.g., "import { Dialog as DialogPrimitive }") and update its module
specifier to '`@radix-ui/react-dialog`' and then run the repo type-check/build to
confirm the component compiles.
| className={cn( | ||
| 'shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch', | ||
| className | ||
| )} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Look for custom-variant definitions for data-horizontal/data-vertical
fd -e css | xargs rg -n -C1 'custom-variant|data-horizontal|data-vertical|data-orientation' 2>/dev/nullRepository: isntboxs/marvticle
Length of output: 251
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Inspect the exact classes used in the separator component
echo "---- separator.tsx ----"
sed -n '1,120p' packages/ui/src/components/separator.tsx
# 2) Inspect globals.css for any custom variants or data-orientation handling
echo "---- globals.css (relevant parts) ----"
sed -n '1,120p' packages/ui/src/styles/globals.css
# 3) Search for any other definitions of custom variants or Tailwind config notes
echo "---- repo search for custom variants / data-orientation / data-horizontal / data-vertical ----"
rg -n "custom-variant|data-orientation|data-horizontal|data-vertical" packages/ui/src -S || true
rg -n "tailwind\.config|`@tailwind`|variants" -S . || trueRepository: isntboxs/marvticle
Length of output: 16601
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- packages/ui/src/components/field.tsx (relevant parts) ----"
sed -n '1,220p' packages/ui/src/components/field.tsx
echo "---- packages/ui/src/components/button-group.tsx (relevant parts) ----"
sed -n '1,140p' packages/ui/src/components/button-group.tsxRepository: isntboxs/marvticle
Length of output: 7891
🌐 Web query:
Tailwind CSS v4 variant syntax "data-horizontal:" what selector does it generate?
💡 Result:
In Tailwind CSS v4, the variant syntax data-horizontal: is not a built-in default; it is a custom variant that must be explicitly defined in your CSS [1][2][3]. When encountered in projects (such as early versions of some UI library components), it is typically defined as a shorthand for the data-orientation="horizontal" attribute selector [3]. If defined in your CSS file, it generates the following CSS selector: css &[data-orientation="horizontal"] You can define this custom variant in your CSS using the @custom-variant directive [4][5]: css `@custom-variant` data-horizontal (&[data-orientation="horizontal"]); If this variant is used in your project without being defined, it will not match any elements, often resulting in styles failing to apply [2][6]. Many modern component libraries have migrated away from these shorthands in favor of the standard, built-in Tailwind data-* syntax, which allows you to target any data attribute directly without custom configuration [7][8]: html <div class="data-[orientation=horizontal]:bg-gray-100"></div> The native data-[key=value] syntax automatically generates the corresponding [data-key="value"] selector [7].
Citations:
- 1: [bug]: shadcn/Create: Tailwind data attribute selector fix: data-horizontal / data-vertical → data-[orientation=horizontal] shadcn-ui/ui#9196
- 2: Fix(separator): use data-[orientation] selectors to fix invisible separator in Tailwind v4 shadcn-ui/ui#9572
- 3: [bug]: Separator component broken by default shadcn-ui/ui#10415
- 4: https://tailwindcss.com/docs/adding-custom-styles
- 5: https://stackoverflow.com/questions/79524055/adding-custom-variants-to-tailwindcss-4
- 6: [bug]: wrong data attributes for base-ui slider shadcn-ui/ui#10480
- 7: https://llmbestpractices.com/frontend/tailwind-variants
- 8: https://tailwindcss.com/blog/tailwindcss-v4
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search entire repo for custom-variant definitions for data-horizontal/data-vertical
rg -n "`@custom-variant`\s+data-(horizontal|vertical)\b|data-horizontal:|data-vertical:" -S . || true
# Also look for any file that defines shadcn/tailwind.css locally in the repo
fd -t f "tailwind.css" -S . | rg -n "shadcn" || trueRepository: isntboxs/marvticle
Length of output: 685
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Search for any custom-variant definitions for data-horizontal/data-vertical
rg -n "`@custom-variant`\s+data-(horizontal|vertical)\b|data-horizontal:|data-vertical:" -S .
# 2) Find any local tailwind.css files in the repo (to see if shadcn/tailwind.css is vendored)
fd -t f -e css -e js -e ts "tailwind.css" -S . || true
fd -t f -e css "tailwind.css" -S . || true
# 3) Narrow search for shadcn/tailwind.css file path if present
fd -t f "tailwind.css" -S . | rg -n "shadcn|tailwind\.css" || trueRepository: isntboxs/marvticle
Length of output: 953
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for any custom-variant definitions for data-horizontal/data-vertical
rg -n "`@custom-variant`\s+data-(horizontal|vertical)\b|data-horizontal:|data-vertical:" -S . || true
# Find any local tailwind.css files (could contain shadcn/tailwind.css vendored content)
fd -t f -e css -e js -e ts "tailwind.css" . || true
fd -t f -e css "shadcn/tailwind.css" . || true
# If any tailwind.css was found, scan them for custom variants
for f in $(fd -t f -e css "tailwind.css" .); do
echo "---- $f ----"
rg -n "`@custom-variant`|data-horizontal|data-vertical|data-orientation" "$f" -S || true
doneRepository: isntboxs/marvticle
Length of output: 533
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- tailwind.css files in repo (if any) ----"
fd -t f "tailwind.css" . || true
echo "---- shadcn directory/files in repo (if any) ----"
fd -t d "shadcn" . || true
fd -t f "shadcn" . || true
echo "---- any file defining custom variants for data-horizontal/data-vertical ----"
rg -n "`@custom-variant`" . -S || true
rg -n "data-horizontal|data-vertical|data-orientation" packages/ui/src/styles -S || trueRepository: isntboxs/marvticle
Length of output: 313
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Try to locate the imported shadcn/tailwind.css within the repository (including node_modules if present)
rg -n "shadcn/tailwind\.css" -S . || true
# Find any tailwind.css that mentions data-horizontal/data-vertical/data-orientation
fd -t f "tailwind.css" . || true
find . -type f -path "*shadcn*" -name "tailwind.css" 2>/dev/null || true
find . -type f -name "*.css" -print0 2>/dev/null | xargs -0 rg -n "data-horizontal:|data-vertical:|data-orientation|`@custom-variant`.*data-(horizontal|vertical)" 2>/dev/null || trueRepository: isntboxs/marvticle
Length of output: 132
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the shadcn/tailwind.css module file if it exists in node_modules
echo "---- candidate shadcn/tailwind.css files ----"
find node_modules -type f -path "*shadcn*" -name "tailwind.css" 2>/dev/null | head -n 50
echo "---- search for custom variants in any shadcn tailwind.css ----"
for f in $(find node_modules -type f -path "*shadcn*" -name "tailwind.css" 2>/dev/null); do
echo "---- $f ----"
rg -n "`@custom-variant`|data-horizontal|data-vertical|data-orientation" "$f" -S || true
done
# Also search node_modules for explicit custom variant declarations (limit to data-horizontal/data-vertical)
echo "---- repo-wide node_modules match for data-horizontal/data-vertical variants ----"
rg -n "`@custom-variant`\s+data-(horizontal|vertical)\b|data-horizontal:|data-vertical:|data-orientation=\"(horizontal|vertical)\"" node_modules -S || trueRepository: isntboxs/marvticle
Length of output: 254
🌐 Web query:
Radix UI SeparatorPrimitive.Root data-orientation attribute orientation="horizontal|vertical"
💡 Result:
The Radix UI Separator primitive exposes a data-orientation attribute on the SeparatorPrimitive.Root component [1][2]. This data attribute automatically updates to reflect the current orientation prop [1], allowing for easy styling based on the separator's direction [3][4]. Key details regarding the data-orientation attribute: Attribute Values: The data-orientation attribute will hold a value of either "horizontal" or "vertical" [1][3]. Styling Usage: You can use this attribute in your CSS or styling solution (such as Tailwind CSS) to apply specific styles based on the orientation [3][4]. For example, in Tailwind CSS, you might use selectors like data-[orientation=horizontal]:h-px and data-[orientation=vertical]:w-px to adjust the thickness and dimensions of the separator accordingly [3]. Default Behavior: The orientation prop defaults to "horizontal" if not explicitly set [1][2]. Accessibility: The component automatically manages accessibility requirements and ensures the appropriate aria attributes are applied for the given orientation [1][3]. Example implementation using data-orientation for styling:.SeparatorRoot[data-orientation='horizontal'] { height: 1px; width: 100%; }.SeparatorRoot[data-orientation='vertical'] { height: 100%; width: 1px; }
Citations:
- 1: https://www.radix-ui.com/primitives/docs/components/separator
- 2: https://github.com/radix-ui/website/blob/main/data/primitives/docs/components/separator.mdx
- 3: https://shadcn-ui-ui.mintlify.app/components/separator
- 4: https://www.radix-ng.com/primitives/components/separator
🏁 Script executed:
rg -n "`@custom-variant`\s+data-(horizontal|vertical)\b" node_modules -S || trueRepository: isntboxs/marvticle
Length of output: 44
Fix Tailwind orientation selectors for Radix Separator
SeparatorPrimitive.Root emits data-orientation="horizontal|vertical", but packages/ui/src/components/separator.tsx uses data-horizontal:* / data-vertical:* utilities. packages/ui/src/styles/globals.css only defines @custom-variant dark and no data-horizontal/data-vertical variants, so the size classes won’t apply.
Switch the classes to Tailwind’s data-selector form (or define matching @custom-variants), e.g. data-[orientation=horizontal]:h-px ... / data-[orientation=vertical]:w-px ....
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/components/separator.tsx` around lines 18 - 21, The Tailwind
data selectors are wrong for SeparatorPrimitive.Root: replace the current
data-horizontal:* and data-vertical:* utilities in the className passed to the
Separator component with Tailwind data attribute selectors that match Radix's
data-orientation, e.g. use data-[orientation=horizontal]:h-px and
data-[orientation=horizontal]:w-full and data-[orientation=vertical]:w-px and
data-[orientation=vertical]:self-stretch (keep the cn(...) wrapper and preserve
the existing 'shrink-0 bg-border' and the passed className).
Summary by CodeRabbit
New Features
UI/UX Improvements